package com.motorola.stylus.view.recyclerview

import android.animation.Animator
import android.animation.ValueAnimator
import android.graphics.Canvas
import android.graphics.Rect
import android.os.Build
import android.util.Log
import android.view.*
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Interpolator
import android.widget.Toast
import androidx.core.animation.doOnStart
import androidx.core.view.*
import androidx.recyclerview.widget.ItemTouchUIUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.*
import com.motorola.stylus.view.recyclerview.ItemTouchHelperPlus.Callback
import com.motorola.stylus.view.recyclerview.ItemTouchHelperPlus.ISwipeViewHolder
import com.ysw.test.R
import kotlin.math.abs

/**
 * Extended system class [androidx.recyclerview.widget.ItemTouchHelper] to support item click after it swiped.
 *
 * Add function: Swipe to show a menu to have some user interaction.
 * Use [ItemTouchHelperPlus.closeOpened] method to control the swiped menu;
 * Use [ISwipeViewHolder.getSwipeMaxWidth] method to control the swipe menu layout max with;
 * Use [canConsumeClickEvent] method before perform click,otherwise there will be conflicts;
 * Use [Callback.getCloseOpenedAnimationDuration] to override close opened menu animation duration.
 *
 * Modify [Callback.isLongPressDragEnabled] and [Callback.isItemViewSwipeEnabled] method,both add one
 * param [RecyclerView.ViewHolder], by this param to judge。
 */
class ItemTouchHelperPlus(
    val mCallback: Callback
) : ItemDecoration(), OnChildAttachStateChangeListener {

    /**
     * Need [RecyclerView.ViewHolder] to inherit
     */
    interface ISwipeViewHolder {
        /**
         * Use to get menu sliding maximum threshold
         */
        fun getSwipeMaxWidth(): Float
    }

    private val mPendingCleanup: MutableList<View> = ArrayList()
    private val mTmpPosition = FloatArray(2)
    private var mSelected: ViewHolder? = null
    private var mPreOpened: ViewHolder? = null
    private var mInitialTouchX = 0f
    private var mInitialTouchY = 0f
    private var mSwipeEscapeVelocity = 0f
    private var mMaxSwipeVelocity = 0f
    private var mDx = 0f
    private var mDy = 0f
    private var mSelectedStartX = 0f
    private var mSelectedStartY = 0f
    private var mActivePointerId = ACTIVE_POINTER_ID_NONE
    private var mActionState = ACTION_STATE_IDLE
    private var mSelectedFlags = 0
    private var mRecoverAnimations = mutableListOf<RecoverAnimation>()
    private var mSlop = 0
    private var mRecyclerView: RecyclerView? = null

    private val mScrollRunnable: Runnable = object : Runnable {
        override fun run() {
            if (mSelected != null && scrollIfNecessary()) {
                if (mSelected != null) { //it might be lost during scrolling
                    moveIfNecessary(mSelected!!)
                }
                mRecyclerView!!.removeCallbacks(this)
                ViewCompat.postOnAnimation(mRecyclerView!!, this)
            }
        }
    }

    private var mVelocityTracker: VelocityTracker? = null

    //re-used list for selecting a swap target
    private var mSwapTargets: MutableList<ViewHolder>? = null

    //re used for for sorting swap targets
    private var mDistances: MutableList<Int>? = null

    private var mChildDrawingOrderCallback: ChildDrawingOrderCallback? = null

    private var mOverdrawChild: View? = null

    private var mOverdrawChildPosition = -1

    private var mGestureDetector: GestureDetectorCompat? = null

    private val mOnItemTouchListener: OnItemTouchListener = object : OnItemTouchListener {

        var markAsClick = false
        var lastX = 0f

        override fun onInterceptTouchEvent(
            recyclerView: RecyclerView,
            event: MotionEvent
        ): Boolean {
            mGestureDetector!!.onTouchEvent(event)
            if (DEBUG) {
                Log.d(TAG, "intercept: x:" + event.x + ",y:" + event.y + ", " + event)
            }
            val action = MotionEventCompat.getActionMasked(event)
            if (action == MotionEvent.ACTION_DOWN) {
                mActivePointerId = MotionEventCompat.getPointerId(event, 0)
                mInitialTouchX = event.x
                mInitialTouchY = event.y
                markAsClick = true
                lastX = event.x
                obtainVelocityTracker()
                if (mSelected == null) {
                    val animation = findAnimation(event)
                    if (animation != null) {
                        mInitialTouchX -= animation.hitX
                        mInitialTouchY -= animation.mY
                        endRecoverAnimation(animation.mViewHolder, true)
                        if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
                            mCallback.clearView(mRecyclerView, animation.mViewHolder)
                        }
                        select(animation.mViewHolder, animation.mActionState)
                        updateDxDy(event, mSelectedFlags, 0)
                    }
                }
            } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                mActivePointerId = ACTIVE_POINTER_ID_NONE
                if (markAsClick && action == MotionEvent.ACTION_UP) {
                    doChildClickEvent(event.rawX, event.rawY)
                }
                select(null, ACTION_STATE_IDLE)
            } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
                // in a non scroll orientation, if distance change is above threshold, we
                // can select the item
                val index = MotionEventCompat.findPointerIndex(event, mActivePointerId)
                if (DEBUG) {
                    Log.d(TAG, "pointer index $index")
                }
                if (index >= 0) {
                    checkSelectForSwipe(action, event, index)
                }
            }
            if (mVelocityTracker != null) {
                mVelocityTracker!!.addMovement(event)
            }
            return mSelected != null
        }

        override fun onTouchEvent(recyclerView: RecyclerView, event: MotionEvent) {
            mGestureDetector!!.onTouchEvent(event)
            if (DEBUG) {
                Log.d(
                    TAG,
                    "on touch: x:$mInitialTouchX,y:$mInitialTouchY, :$event"
                )
            }
            if (mVelocityTracker != null) {
                mVelocityTracker!!.addMovement(event)
            }
            if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
                return
            }
            val action = MotionEventCompat.getActionMasked(event)
            val activePointerIndex = MotionEventCompat
                .findPointerIndex(event, mActivePointerId)
            if (activePointerIndex >= 0) {
                checkSelectForSwipe(action, event, activePointerIndex)
            }
            val viewHolder = mSelected ?: return
            when (action) {
                MotionEvent.ACTION_MOVE -> {


                    // Find the index of the active pointer and fetch its position
                    if (activePointerIndex >= 0) {
                        updateDxDy(event, mSelectedFlags, activePointerIndex)
                        if (abs(event.x - lastX) > mSlop) {
                            markAsClick = false
                        }
                        lastX = event.x
                        moveIfNecessary(viewHolder)
                        mRecyclerView!!.removeCallbacks(mScrollRunnable)
                        mScrollRunnable.run()
                        mRecyclerView!!.invalidate()
                    }
                }
                MotionEvent.ACTION_CANCEL -> {
                    if (mVelocityTracker != null) {
                        mVelocityTracker!!.clear()
                    }
                    if (markAsClick) {
                        doChildClickEvent(event.rawX, event.rawY)
                    }
                    markAsClick = false
                    select(null, ACTION_STATE_IDLE)
                    mActivePointerId = ACTIVE_POINTER_ID_NONE
                }
                MotionEvent.ACTION_UP -> {
                    if (markAsClick) {
                        doChildClickEvent(event.rawX, event.rawY)
                    }
                    markAsClick = false
                    select(null, ACTION_STATE_IDLE)
                    mActivePointerId = ACTIVE_POINTER_ID_NONE
                }
                MotionEvent.ACTION_POINTER_UP -> {
                    markAsClick = false
                    val pointerIndex = MotionEventCompat.getActionIndex(event)
                    val pointerId = MotionEventCompat.getPointerId(event, pointerIndex)
                    if (pointerId == mActivePointerId) {
                        // This was our active pointer going up. Choose a new
                        // active pointer and adjust accordingly.
                        val newPointerIndex = if (pointerIndex == 0) 1 else 0
                        mActivePointerId = MotionEventCompat.getPointerId(event, newPointerIndex)
                        updateDxDy(event, mSelectedFlags, pointerIndex)
                    }
                }
                else -> {}
            }
        }

        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
            if (!disallowIntercept) {
                return
            }
            select(null, ACTION_STATE_IDLE)
        }
    }

    /**
     * Control swipe menu
     */
    fun closeOpened() {
        closeOpenedPreItem()
    }

    private fun closeOpenedPreItem() {
        val view = mCallback.getItemFrontView(mPreOpened)
        if (mPreOpened == null || view == null) return
        ValueAnimator.ofFloat(view.translationX, 0f).apply {
            interpolator = AccelerateDecelerateInterpolator()
            duration = mCallback.getCloseOpenedAnimationDuration()
            addUpdateListener {
                view.translationX = it.animatedValue as Float
            }
            doOnStart {
                if (mPreOpened != null) mCallback.clearView(mRecyclerView, mPreOpened!!)
                if (mPreOpened != null) mPendingCleanup.remove(mPreOpened!!.itemView)
                endRecoverAnimation(mPreOpened, true)
                mPreOpened = mSelected
            }
            start()
        }
    }

    /**
     * When executing the menu click event, you need to call this method to determine whether
     * the current vh can consume the click event, mainly to solve the click event that is not taken
     * over by ItemTouchHelperPlus. When it's not ItemTouchHelperPlus to manually execute click
     *
     * @param clickedViewHolder ViewHolder
     * @return Boolean
     */
    fun canConsumeClickEvent(clickedViewHolder: ViewHolder): Boolean {
        return mPreOpened == clickedViewHolder
    }

    private fun doChildClickEvent(x: Float, y: Float) {
        if (mSelected == null) return
        var consumeEventView: View? = mSelected!!.itemView
        if (consumeEventView is ViewGroup) {
            consumeEventView = findConsumeView(consumeEventView, x, y)
        }
        consumeEventView?.performClick()
    }

    private fun findConsumeView(parent: ViewGroup, x: Float, y: Float): View? {
        for (i in 0 until parent.childCount) {
            val child = parent.getChildAt(i)
            if (child is ViewGroup && child.isVisible) {
                val view = findConsumeView(child, x, y)
                if (view != null) {
                    return view
                }
            } else {
                if (isInBoundsClickable(x.toInt(), y.toInt(), child)) return child
            }
        }
        return if (isInBoundsClickable(x.toInt(), y.toInt(), parent)) parent else null
    }

    private fun isInBoundsClickable(x: Int, y: Int, child: View): Boolean {
        val location = IntArray(2)
        child.getLocationOnScreen(location)
        val rect =
            Rect(location[0], location[1], location[0] + child.width, location[1] + child.height)
        return if (rect.contains(x, y) && ViewCompat.hasOnClickListeners(child)
            && child.visibility == View.VISIBLE
        ) {
            true
        } else false
    }


    private var mTmpRect: Rect? = null


    private var mDragScrollStartTimeInMs: Long = 0


    fun attachToRecyclerView(recyclerView: RecyclerView?) {
        if (mRecyclerView === recyclerView) {
            return  // nothing to do
        }
        if (mRecyclerView != null) {
            destroyCallbacks()
        }
        mRecyclerView = recyclerView
        if (mRecyclerView != null) {
            val resources = recyclerView!!.resources
            mSwipeEscapeVelocity = resources.getDimension(R.dimen.dp_120)
            mMaxSwipeVelocity =
                resources.getDimension(R.dimen.dp_500) + resources.getDimension(R.dimen.dp_300)
            setupCallbacks()

            mRecyclerView!!.addOnScrollListener(object : RecyclerView.OnScrollListener() {
                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    super.onScrollStateChanged(recyclerView, newState)
                    if (newState == SCROLL_STATE_DRAGGING && mPreOpened != null) {
                        closeOpenedPreItem()
                    }
                }
            })
        }
    }

    private fun setupCallbacks() {
        val vc = ViewConfiguration.get(mRecyclerView!!.context)
        mSlop = vc.scaledTouchSlop
        mRecyclerView!!.addItemDecoration(this)
        mRecyclerView!!.addOnItemTouchListener(mOnItemTouchListener)
        mRecyclerView!!.addOnChildAttachStateChangeListener(this)
        initGestureDetector()
    }

    private fun destroyCallbacks() {
        mRecyclerView!!.removeItemDecoration(this)
        mRecyclerView!!.removeOnItemTouchListener(mOnItemTouchListener)
        mRecyclerView!!.removeOnChildAttachStateChangeListener(this)
        // clean all attached
        val recoverAnimSize = mRecoverAnimations.size
        for (i in recoverAnimSize - 1 downTo 0) {
            val recoverAnimation = mRecoverAnimations[0]
            mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder)
        }
        mRecoverAnimations.clear()
        mOverdrawChild = null
        mOverdrawChildPosition = -1
        releaseVelocityTracker()
    }

    private fun initGestureDetector() {
        if (mGestureDetector != null) {
            return
        }
        mGestureDetector = GestureDetectorCompat(
            mRecyclerView!!.context,
            ItemTouchHelperGestureListener()
        )
    }

    private fun getSelectedDxDy(outPosition: FloatArray) {
        if (mSelectedFlags and (LEFT or RIGHT) != 0) {
            outPosition[0] = mSelectedStartX + mDx - mSelected!!.itemView.left
        } else {
            outPosition[0] = ViewCompat.getTranslationX(mSelected!!.itemView)
        }
        if (mSelectedFlags and (UP or DOWN) != 0) {
            outPosition[1] = mSelectedStartY + mDy - mSelected!!.itemView.top
        } else {
            outPosition[1] = ViewCompat.getTranslationY(mSelected!!.itemView)
        }
    }

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: State) {
        var dx = 0f
        var dy = 0f
        if (mSelected != null) {
            getSelectedDxDy(mTmpPosition)
            dx = mTmpPosition[0]
            dy = mTmpPosition[1]
        }
        mCallback.onDrawOver(
            c, parent, mSelected,
            mRecoverAnimations, mActionState, dx, dy
        )
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: State) {
        // we don't know if RV changed something so we should invalidate this index.
        mOverdrawChildPosition = -1
        var dx = 0f
        var dy = 0f
        if (mSelected != null) {
            getSelectedDxDy(mTmpPosition)
            dx = mTmpPosition[0]
            dy = mTmpPosition[1]
        }
        mCallback.onDraw(
            c, parent, mSelected,
            mRecoverAnimations, mActionState, dx, dy
        )
    }

    private fun select(selected: ViewHolder?, actionState: Int) {
        if (selected === mSelected && actionState == mActionState) {
            return
        }
        mDragScrollStartTimeInMs = Long.MIN_VALUE
        val prevActionState = mActionState
        // prevent duplicate animations
        endRecoverAnimation(selected, true)
        mActionState = actionState
        if (actionState == ACTION_STATE_DRAG) {
            // we remove after animation is complete. this means we only elevate the last drag
            // child but that should perform good enough as it is very hard to start dragging a
            // new child before the previous one settles.
            mOverdrawChild = selected!!.itemView
            addChildDrawingOrderCallback()
        }
        val actionStateMask = ((1 shl DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)
                - 1)
        var preventLayout = false
        if (mSelected != null) {
            val prevSelected: ViewHolder = mSelected as ViewHolder
            if (prevSelected.itemView.parent != null) {
                val swipeDir =
                    if (prevActionState == ACTION_STATE_DRAG) 0 else swipeIfNecessary(prevSelected)
                releaseVelocityTracker()
                // find where we should animate to
                val targetTranslateX: Float
                val targetTranslateY: Float
                val animationType: Int
                when (swipeDir) {
                    LEFT, RIGHT, START, END -> {
                        targetTranslateY = 0f
                        targetTranslateX = Math.signum(mDx) * getSwipeWidth()
                    }
                    UP, DOWN -> {
                        targetTranslateX = 0f
                        targetTranslateY = Math.signum(mDy) * mRecyclerView!!.height
                    }
                    else -> {
                        targetTranslateX = 0f
                        targetTranslateY = 0f
                    }
                }
                animationType = if (prevActionState == ACTION_STATE_DRAG) {
                    ANIMATION_TYPE_DRAG
                } else if (swipeDir > 0) {
                    ANIMATION_TYPE_SWIPE_SUCCESS
                } else {
                    ANIMATION_TYPE_SWIPE_CANCEL
                }
                getSelectedDxDy(mTmpPosition)

                val currentTranslateX = mTmpPosition[0]
                val currentTranslateY = mTmpPosition[1]

                val rv: RecoverAnimation = object : RecoverAnimation(
                    prevSelected, animationType,
                    prevActionState, currentTranslateX, currentTranslateY,
                    targetTranslateX, targetTranslateY
                ) {
                    override fun onAnimationEnd(animation: Animator) {
                        super.onAnimationEnd(animation)
                        if (mOverridden) {
                            return
                        }
                        if (swipeDir <= 0) {
                            // this is a drag or failed swipe. recover immediately
                            mCallback.clearView(mRecyclerView, prevSelected)
                        } else {
                            // wait until remove animation is complete.
                            mPendingCleanup.add(prevSelected.itemView)
                            mPreOpened = prevSelected
                            mIsPendingCleanup = true
                            if (swipeDir > 0) {
                                // Animation might be ended by other animators during a layout.
                                // We defer callback to avoid editing adapter during a layout.
                                postDispatchSwipe(this, swipeDir)
                            }
                        }
                        // removed from the list after it is drawn for the last time
                        if (mOverdrawChild === prevSelected.itemView) {
                            removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView)
                        }
                    }
                }
                val duration = mCallback.getAnimationDuration(
                    mRecyclerView, animationType,
                    targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY
                )
                rv.setDuration(duration)
                mRecoverAnimations.add(rv)
                rv.start()
                preventLayout = true
            } else {
                removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView)
                mCallback.clearView(mRecyclerView, prevSelected)
            }
            mSelected = null
        }
        if (selected != null) {
            mSelectedFlags =
                (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) and actionStateMask
                        shr mActionState * DIRECTION_FLAG_COUNT)
            mSelectedStartX = selected.itemView.left.toFloat()
            mSelectedStartY = selected.itemView.top.toFloat()
            mSelected = selected
            if (actionState == ACTION_STATE_DRAG) {
                mSelected!!.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
            }
        }
        val rvParent = mRecyclerView!!.parent
        rvParent?.requestDisallowInterceptTouchEvent(mSelected != null)
        if (!preventLayout) {
            mRecyclerView!!.layoutManager!!.requestSimpleAnimationsInNextLayout()
        }
        mCallback.onSelectedChanged(mSelected, mActionState)
        mRecyclerView!!.invalidate()
    }

    private fun getSwipeWidth(): Float {
        return if (mSelected is ISwipeViewHolder) (mSelected as ISwipeViewHolder).getSwipeMaxWidth()
        else mRecyclerView!!.width.toFloat()
    }

    private fun postDispatchSwipe(anim: RecoverAnimation, swipeDir: Int) {
        // wait until animations are complete.
        mRecyclerView!!.post(object : Runnable {
            override fun run() {
                if (mRecyclerView != null && mRecyclerView!!.isAttachedToWindow &&
                    !anim.mOverridden && anim.mViewHolder.adapterPosition != NO_POSITION
                ) {
                    val animator = mRecyclerView!!.itemAnimator
                    // if animator is running or we have other active recover animations, we try
                    // not to call onSwiped because DefaultItemAnimator is not good at merging
                    // animations. Instead, we wait and batch.
                    if ((animator == null || !animator.isRunning(null))
                        && !hasRunningRecoverAnim()
                    ) {
                        mCallback.onSwiped(anim.mViewHolder, swipeDir)
                    } else {
                        mRecyclerView!!.post(this)
                    }
                }
            }
        })
    }

    private fun hasRunningRecoverAnim(): Boolean {
        val size = mRecoverAnimations.size
        for (i in 0 until size) {
            if (!mRecoverAnimations[i].mEnded) {
                return true
            }
        }
        return false
    }

    private fun scrollIfNecessary(): Boolean {
        if (mSelected == null) {
            mDragScrollStartTimeInMs = Long.MIN_VALUE
            return false
        }
        val now = System.currentTimeMillis()
        val scrollDuration = if (mDragScrollStartTimeInMs
            == Long.MIN_VALUE
        ) 0 else now - mDragScrollStartTimeInMs
        val lm = mRecyclerView!!.layoutManager
        if (mTmpRect == null) {
            mTmpRect = Rect()
        }
        var scrollX = 0
        var scrollY = 0
        lm!!.calculateItemDecorationsForChild(mSelected!!.itemView, mTmpRect!!)
        if (lm.canScrollHorizontally()) {
            val curX = (mSelectedStartX + mDx).toInt()
            val leftDiff = curX - mTmpRect!!.left - mRecyclerView!!.paddingLeft
            if (mDx < 0 && leftDiff < 0) {
                scrollX = leftDiff
            } else if (mDx > 0) {
                val rightDiff = (curX + mSelected!!.itemView.width + mTmpRect!!.right
                        - (mRecyclerView!!.width - mRecyclerView!!.paddingRight))
                if (rightDiff > 0) {
                    scrollX = rightDiff
                }
            }
        }
        if (lm.canScrollVertically()) {
            val curY = (mSelectedStartY + mDy).toInt()
            val topDiff = curY - mTmpRect!!.top - mRecyclerView!!.paddingTop
            if (mDy < 0 && topDiff < 0) {
                scrollY = topDiff
            } else if (mDy > 0) {
                val bottomDiff = curY + mSelected!!.itemView.height + mTmpRect!!.bottom -
                        (mRecyclerView!!.height - mRecyclerView!!.paddingBottom)
                if (bottomDiff > 0) {
                    scrollY = bottomDiff
                }
            }
        }
        if (scrollX != 0) {
            scrollX = mCallback.interpolateOutOfBoundsScroll(
                mRecyclerView,
                mSelected!!.itemView.width, scrollX,
                mRecyclerView!!.width, scrollDuration
            )
        }
        if (scrollY != 0) {
            scrollY = mCallback.interpolateOutOfBoundsScroll(
                mRecyclerView,
                mSelected!!.itemView.height, scrollY,
                mRecyclerView!!.height, scrollDuration
            )
        }
        if (scrollX != 0 || scrollY != 0) {
            if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
                mDragScrollStartTimeInMs = now
            }
            mRecyclerView!!.scrollBy(scrollX, scrollY)
            return true
        }
        mDragScrollStartTimeInMs = Long.MIN_VALUE
        return false
    }

    private fun findSwapTargets(viewHolder: ViewHolder): List<ViewHolder> {
        if (mSwapTargets == null) {
            mSwapTargets = ArrayList()
            mDistances = ArrayList()
        } else {
            mSwapTargets!!.clear()
            mDistances!!.clear()
        }
        val margin = mCallback.getBoundingBoxMargin()
        val left = Math.round(mSelectedStartX + mDx) - margin
        val top = Math.round(mSelectedStartY + mDy) - margin
        val right = left + viewHolder.itemView.width + 2 * margin
        val bottom = top + viewHolder.itemView.height + 2 * margin
        val centerX = (left + right) / 2
        val centerY = (top + bottom) / 2
        val lm = mRecyclerView!!.layoutManager
        val childCount = lm!!.childCount
        for (i in 0 until childCount) {
            val other = lm.getChildAt(i)
            if (other === viewHolder.itemView) {
                continue  //myself!
            }
            if (other!!.bottom < top || other.top > bottom || other.right < left || other.left > right) {
                continue
            }
            val otherVh = mRecyclerView!!.getChildViewHolder(other)
            if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) {
                // find the index to add
                val dx = Math.abs(centerX - (other.left + other.right) / 2)
                val dy = Math.abs(centerY - (other.top + other.bottom) / 2)
                val dist = dx * dx + dy * dy
                var pos = 0
                val cnt = mSwapTargets!!.size
                for (j in 0 until cnt) {
                    if (dist > mDistances!![j]) {
                        pos++
                    } else {
                        break
                    }
                }
                mSwapTargets!!.add(pos, otherVh)
                mDistances!!.add(pos, dist)
            }
        }
        return mSwapTargets!!
    }

    private fun moveIfNecessary(viewHolder: ViewHolder) {
        if (mRecyclerView!!.isLayoutRequested) {
            return
        }
        if (mActionState != ACTION_STATE_DRAG) {
            return
        }
        val threshold = mCallback.getMoveThreshold(viewHolder)
        val x = (mSelectedStartX + mDx).toInt()
        val y = (mSelectedStartY + mDy).toInt()
        if (Math.abs(y - viewHolder.itemView.top) < viewHolder.itemView.height * threshold
            && Math.abs(x - viewHolder.itemView.left)
            < viewHolder.itemView.width * threshold
        ) {
            return
        }
        val swapTargets = findSwapTargets(viewHolder)
        if (swapTargets.size == 0) {
            return
        }
        // may swap.
        val target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y)
        if (target == null) {
            mSwapTargets!!.clear()
            mDistances!!.clear()
            return
        }
        val toPosition = target.adapterPosition
        val fromPosition = viewHolder.adapterPosition
        if (mCallback.onMove(mRecyclerView!!, viewHolder, target)) {
            // keep target visible
            mCallback.onMoved(
                mRecyclerView, viewHolder, fromPosition,
                target, toPosition, x, y
            )
        }
    }

    override fun onChildViewAttachedToWindow(view: View) {}

    override fun onChildViewDetachedFromWindow(view: View) {
        removeChildDrawingOrderCallbackIfNecessary(view)
        val holder = mRecyclerView!!.getChildViewHolder(view) ?: return
        if (mSelected != null && holder === mSelected) {
            select(null, ACTION_STATE_IDLE)
        } else {
            endRecoverAnimation(holder, false) // this may push it into pending cleanup list.
            if (mPendingCleanup.remove(holder.itemView)) {
                mCallback.clearView(mRecyclerView, holder)
            }
        }
    }

    private fun endRecoverAnimation(viewHolder: ViewHolder?, override: Boolean): Int {
        val recoverAnimSize = mRecoverAnimations.size
        for (i in recoverAnimSize - 1 downTo 0) {
            val anim = mRecoverAnimations[i]
            if (anim.mViewHolder === viewHolder) {
                anim.mOverridden = anim.mOverridden or override
                if (!anim.mEnded) {
                    anim.cancel()
                }
                mRecoverAnimations.removeAt(i)
                return anim.mAnimationType
            }
        }
        return 0
    }

    override fun getItemOffsets(
        outRect: Rect, view: View, parent: RecyclerView,
        state: State
    ) {
        outRect.setEmpty()
    }

    private fun obtainVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker!!.recycle()
        }
        mVelocityTracker = VelocityTracker.obtain()
    }

    private fun releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker!!.recycle()
            mVelocityTracker = null
        }
    }

    private fun findSwipedView(motionEvent: MotionEvent): ViewHolder? {
        val lm = mRecyclerView!!.layoutManager
        if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
            return null
        }
        val pointerIndex = MotionEventCompat.findPointerIndex(motionEvent, mActivePointerId)
        val dx = MotionEventCompat.getX(motionEvent, pointerIndex) - mInitialTouchX
        val dy = MotionEventCompat.getY(motionEvent, pointerIndex) - mInitialTouchY
        val absDx = Math.abs(dx)
        val absDy = Math.abs(dy)
        if (absDx < mSlop && absDy < mSlop) {
            return null
        }
        if (absDx > absDy && lm!!.canScrollHorizontally()) {
            return null
        } else if (absDy > absDx && lm!!.canScrollVertically()) {
            return null
        }
        val child = findChildView(motionEvent) ?: return null
        return mRecyclerView!!.getChildViewHolder(child)
    }

    private fun checkSelectForSwipe(
        action: Int,
        motionEvent: MotionEvent,
        pointerIndex: Int
    ): Boolean {
        if (mSelected != null || action != MotionEvent.ACTION_MOVE || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled(
                findSwipedView(motionEvent)
            )
        ) {
            return false
        }
        if (mRecyclerView!!.scrollState == SCROLL_STATE_DRAGGING) {
            return false
        }
        val vh = findSwipedView(motionEvent) ?: return false
        val movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh)
        val swipeFlags = (movementFlags and ACTION_MODE_SWIPE_MASK
                shr DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE)
        if (swipeFlags == 0) {
            return false
        }

        // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
        // updateDxDy to avoid swiping if user moves more in the other direction
        val x = MotionEventCompat.getX(motionEvent, pointerIndex)
        val y = MotionEventCompat.getY(motionEvent, pointerIndex)

        // Calculate the distance moved
        val dx = x - mInitialTouchX
        val dy = y - mInitialTouchY
        // swipe target is chose w/o applying flags so it does not really check if swiping in that
        // direction is allowed. This why here, we use mDx mDy to check slope value again.
        val absDx = Math.abs(dx)
        val absDy = Math.abs(dy)
        if (absDx < mSlop && absDy < mSlop) {
            return false
        }
        if (absDx > absDy) {
            if (dx < 0 && swipeFlags and LEFT == 0) {
                return false
            }
            if (dx > 0 && swipeFlags and RIGHT == 0) {
                return false
            }
        } else {
            if (dy < 0 && swipeFlags and UP == 0) {
                return false
            }
            if (dy > 0 && swipeFlags and DOWN == 0) {
                return false
            }
        }
        mDy = 0f
        mDx = mDy
        mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0)
        select(vh, ACTION_STATE_SWIPE)
        // clear open
        if (mPreOpened != null && mPreOpened !== vh) {
            closeOpenedPreItem()
        }
        return true
    }

    private fun findChildView(event: MotionEvent): View? {
        // first check elevated views, if none, then call RV
        val x = event.x
        val y = event.y
        if (mSelected != null) {
            val selectedView = mSelected!!.itemView
            if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
                return selectedView
            }
        }
        for (i in mRecoverAnimations.indices.reversed()) {
            val anim = mRecoverAnimations[i]
            val view = anim.mViewHolder.itemView
            if (hitTest(view, x, y, anim.hitX, anim.hitY)) {
                return view
            }
        }
        return mRecyclerView!!.findChildViewUnder(x, y)
    }

    fun startDrag(viewHolder: ViewHolder) {
        if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
            Log.e(TAG, "Start drag has been called but swiping is not enabled")
            return
        }
        if (viewHolder.itemView.parent !== mRecyclerView) {
            Log.e(
                TAG, "Start drag has been called with a view holder which is not a child of "
                        + "the RecyclerView which is controlled by this ItemTouchHelper."
            )
            return
        }
        obtainVelocityTracker()
        mDy = 0f
        mDx = mDy
        select(viewHolder, ACTION_STATE_DRAG)
    }

    fun startSwipe(viewHolder: ViewHolder) {
        if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
            Log.e(TAG, "Start swipe has been called but dragging is not enabled")
            return
        }
        if (viewHolder.itemView.parent !== mRecyclerView) {
            Log.e(
                TAG, "Start swipe has been called with a view holder which is not a child of "
                        + "the RecyclerView controlled by this ItemTouchHelper."
            )
            return
        }
        obtainVelocityTracker()
        mDy = 0f
        mDx = mDy
        select(viewHolder, ACTION_STATE_SWIPE)
    }

    private fun findAnimation(event: MotionEvent): RecoverAnimation? {
        if (mRecoverAnimations.isEmpty()) {
            return null
        }
        val target = findChildView(event)
        for (i in mRecoverAnimations.indices.reversed()) {
            val anim = mRecoverAnimations[i]
            if (anim.mViewHolder.itemView === target) {
                return anim
            }
        }
        return null
    }

    private fun updateDxDy(ev: MotionEvent, directionFlags: Int, pointerIndex: Int) {
        val x = ev.getX(pointerIndex)
        val y = ev.getY(pointerIndex)

        // Calculate the distance moved
        mDx = x - mInitialTouchX
        mDy = y - mInitialTouchY
        if (directionFlags and LEFT == 0) {
            mDx = Math.max(0f, mDx)
        }
        if (directionFlags and RIGHT == 0) {
            mDx = Math.min(0f, mDx)
        }
        if (directionFlags and UP == 0) {
            mDy = Math.max(0f, mDy)
        }
        if (directionFlags and DOWN == 0) {
            mDy = Math.min(0f, mDy)
        }
    }

    private fun swipeIfNecessary(viewHolder: ViewHolder): Int {
        if (mActionState == ACTION_STATE_DRAG) {
            return 0
        }
        val originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder)
        val absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
            originalMovementFlags,
            ViewCompat.getLayoutDirection(mRecyclerView!!)
        )
        val flags = (absoluteMovementFlags
                and ACTION_MODE_SWIPE_MASK) shr ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT
        if (flags == 0) {
            return 0
        }
        val originalFlags = (originalMovementFlags
                and ACTION_MODE_SWIPE_MASK) shr ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT
        var swipeDir: Int
        if (Math.abs(mDx) > Math.abs(mDy)) {
            if (checkHorizontalSwipe(viewHolder, flags).also { swipeDir = it } > 0) {
                // if swipe dir is not in original flags, it should be the relative direction
                return if (originalFlags and swipeDir == 0) {
                    // convert to relative
                    Callback.convertToRelativeDirection(
                        swipeDir,
                        ViewCompat.getLayoutDirection(mRecyclerView!!)
                    )
                } else swipeDir
            }
            if (checkVerticalSwipe(viewHolder, flags).also { swipeDir = it } > 0) {
                return swipeDir
            }
        } else {
            if (checkVerticalSwipe(viewHolder, flags).also { swipeDir = it } > 0) {
                return swipeDir
            }
            if (checkHorizontalSwipe(viewHolder, flags).also { swipeDir = it } > 0) {
                // if swipe dir is not in original flags, it should be the relative direction
                return if (originalFlags and swipeDir == 0) {
                    // convert to relative
                    Callback.convertToRelativeDirection(
                        swipeDir,
                        ViewCompat.getLayoutDirection(mRecyclerView!!)
                    )
                } else swipeDir
            }
        }
        return 0
    }

    private fun checkHorizontalSwipe(viewHolder: ViewHolder, flags: Int): Int {
        if (flags and (LEFT or RIGHT) != 0) {
            val dirFlag = if (mDx > 0) RIGHT else LEFT
            if (mVelocityTracker != null && mActivePointerId > -1) {
                mVelocityTracker!!.computeCurrentVelocity(
                    PIXELS_PER_SECOND,
                    mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)
                )
                val xVelocity = VelocityTrackerCompat
                    .getXVelocity(mVelocityTracker, mActivePointerId)
                val yVelocity = VelocityTrackerCompat
                    .getYVelocity(mVelocityTracker, mActivePointerId)
                val velDirFlag = if (xVelocity > 0f) RIGHT else LEFT
                val absXVelocity = Math.abs(xVelocity)
                if (velDirFlag and flags != 0 && dirFlag == velDirFlag && absXVelocity >= mCallback.getSwipeEscapeVelocity(
                        mSwipeEscapeVelocity
                    ) && absXVelocity > Math.abs(yVelocity)
                ) {
                    return velDirFlag
                }
            }
            val threshold = getSwipeWidth() * mCallback
                .getSwipeThreshold(viewHolder)
            if (flags and dirFlag != 0 && Math.abs(mDx) > threshold) {
                return dirFlag
            }
        }
        return 0
    }

    private fun checkVerticalSwipe(viewHolder: ViewHolder, flags: Int): Int {
        if (flags and (UP or DOWN) != 0) {
            val dirFlag = if (mDy > 0) DOWN else UP
            if (mVelocityTracker != null && mActivePointerId > -1) {
                mVelocityTracker!!.computeCurrentVelocity(
                    PIXELS_PER_SECOND,
                    mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)
                )
                val xVelocity = VelocityTrackerCompat
                    .getXVelocity(mVelocityTracker, mActivePointerId)
                val yVelocity = VelocityTrackerCompat
                    .getYVelocity(mVelocityTracker, mActivePointerId)
                val velDirFlag = if (yVelocity > 0f) DOWN else UP
                val absYVelocity = Math.abs(yVelocity)
                if (velDirFlag and flags != 0 && velDirFlag == dirFlag && absYVelocity >= mCallback.getSwipeEscapeVelocity(
                        mSwipeEscapeVelocity
                    ) && absYVelocity > Math.abs(xVelocity)
                ) {
                    return velDirFlag
                }
            }
            val threshold = mRecyclerView!!.height * mCallback
                .getSwipeThreshold(viewHolder)
            if (flags and dirFlag != 0 && Math.abs(mDy) > threshold) {
                return dirFlag
            }
        }
        return 0
    }

    private fun addChildDrawingOrderCallback() {
        if (Build.VERSION.SDK_INT >= 21) {
            return  // we use elevation on Lollipop
        }
        if (mChildDrawingOrderCallback == null) {
            mChildDrawingOrderCallback = ChildDrawingOrderCallback { childCount, i ->
                if (mOverdrawChild == null) {
                    return@ChildDrawingOrderCallback i
                }
                var childPosition = mOverdrawChildPosition
                if (childPosition == -1) {
                    childPosition = mRecyclerView!!.indexOfChild(mOverdrawChild)
                    mOverdrawChildPosition = childPosition
                }
                if (i == childCount - 1) {
                    return@ChildDrawingOrderCallback childPosition
                }
                if (i < childPosition) i else i + 1
            }
        }
        mRecyclerView!!.setChildDrawingOrderCallback(mChildDrawingOrderCallback)
    }

    private fun removeChildDrawingOrderCallbackIfNecessary(view: View) {
        if (view === mOverdrawChild) {
            mOverdrawChild = null
            // only remove if we've added
            if (mChildDrawingOrderCallback != null) {
                mRecyclerView!!.setChildDrawingOrderCallback(null)
            }
        }
    }

    /**
     * An interface which can be implemented by LayoutManager for better integration with
     * [ItemTouchHelperPlus].
     */
    interface ViewDropHandler {
        /**
         * Called by the [ItemTouchHelperPlus] after a View is dropped over another View.
         *
         *
         * A LayoutManager should implement this interface to get ready for the upcoming move
         * operation.
         *
         *
         * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
         * the View under drag will be used as an anchor View while calculating the next layout,
         * making layout stay consistent.
         *
         * @param view   The View which is being dragged. It is very likely that user is still
         * dragging this View so there might be other
         * [.prepareForDrop] after this one.
         * @param target The target view which is being dropped on.
         * @param x      The `left` offset of the View that is being dragged. This value
         * includes the movement caused by the user.
         * @param y      The `top` offset of the View that is being dragged. This value
         * includes the movement caused by the user.
         */
        fun prepareForDrop(view: View?, target: View?, x: Int, y: Int)
    }

    abstract class Callback {
        private var mCachedMaxScrollSpeed = -1

        companion object {
            const val DEFAULT_CLOSE_OPENED_ANIMATION_DURATION = 100L
            const val DEFAULT_DRAG_ANIMATION_DURATION = 200L
            const val DEFAULT_SWIPE_ANIMATION_DURATION = 250L
            const val RELATIVE_DIR_FLAGS =
                START or END or (START or END shl DIRECTION_FLAG_COUNT) or (START or END shl 2 * DIRECTION_FLAG_COUNT)

            var defaultUIUtil: ItemTouchUIUtil? = null

            private const val ABS_HORIZONTAL_DIR_FLAGS =
                LEFT or RIGHT or (LEFT or RIGHT shl DIRECTION_FLAG_COUNT) or (LEFT or RIGHT shl 2 * DIRECTION_FLAG_COUNT)

            private val sDragScrollInterpolator = Interpolator { t -> t * t * t * t * t }

            private val sDragViewScrollCapInterpolator = Interpolator { t ->
                var t = t
                t -= 1.0f
                t * t * t * t * t + 1.0f
            }

            private const val DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS: Long = 2000

            fun convertToRelativeDirection(flags: Int, layoutDirection: Int): Int {
                var flags = flags
                val masked = flags and ABS_HORIZONTAL_DIR_FLAGS
                if (masked == 0) {
                    return flags // does not have any abs flags, good.
                }
                flags = flags and masked.inv() //remove left / right.
                if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
                    // no change. just OR with 2 bits shifted mask and return
                    flags =
                        flags or (masked shl 2) // START is 2 bits after LEFT, END is 2 bits after RIGHT.
                    return flags
                } else {
                    // add RIGHT flag as START
                    flags = flags or (masked shl 1 and ABS_HORIZONTAL_DIR_FLAGS.inv())
                    // first clean RIGHT bit then add LEFT flag as END
                    flags = flags or (masked shl 1 and ABS_HORIZONTAL_DIR_FLAGS shl 2)
                }
                return flags
            }

            @JvmStatic
            fun makeMovementFlags(dragFlags: Int, swipeFlags: Int): Int {
                return makeFlag(ACTION_STATE_IDLE, swipeFlags or dragFlags) or
                        makeFlag(ACTION_STATE_SWIPE, swipeFlags) or makeFlag(
                    ACTION_STATE_DRAG,
                    dragFlags
                )
            }

            fun makeFlag(actionState: Int, directions: Int): Int {
                return directions shl actionState * DIRECTION_FLAG_COUNT
            }

            init {
                defaultUIUtil = ItemTouchUIUtilImplKt.INSTANCE
            }
        }

        open fun getCloseOpenedAnimationDuration(): Long {
            return DEFAULT_CLOSE_OPENED_ANIMATION_DURATION
        }

        abstract fun getMovementFlags(
            recyclerView: RecyclerView?,
            viewHolder: ViewHolder?
        ): Int

        fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int {
            var flags = flags
            val masked = flags and RELATIVE_DIR_FLAGS
            if (masked == 0) {
                return flags // does not have any relative flags, good.
            }
            flags = flags and masked.inv() //remove start / end
            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
                // no change. just OR with 2 bits shifted mask and return
                flags =
                    flags or (masked shr 2) // START is 2 bits after LEFT, END is 2 bits after RIGHT.
                return flags
            } else {
                // add START flag as RIGHT
                flags = flags or (masked shr 1 and RELATIVE_DIR_FLAGS.inv())
                // first clean start bit then add END flag as LEFT
                flags = flags or (masked shr 1 and RELATIVE_DIR_FLAGS shr 2)
            }
            return flags
        }

        fun getAbsoluteMovementFlags(
            recyclerView: RecyclerView?,
            viewHolder: ViewHolder?
        ): Int {
            val flags = getMovementFlags(recyclerView, viewHolder)
            return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView!!))
        }

        fun hasDragFlag(recyclerView: RecyclerView?, viewHolder: ViewHolder): Boolean {
            val flags = getAbsoluteMovementFlags(recyclerView, viewHolder)
            return flags and ACTION_MODE_DRAG_MASK != 0
        }

        fun hasSwipeFlag(
            recyclerView: RecyclerView?,
            viewHolder: ViewHolder
        ): Boolean {
            val flags = getAbsoluteMovementFlags(recyclerView, viewHolder)
            return flags and ACTION_MODE_SWIPE_MASK != 0
        }

        open fun canDropOver(
            recyclerView: RecyclerView?, current: ViewHolder?,
            target: ViewHolder?
        ): Boolean {
            return true
        }

        abstract fun onMove(
            recyclerView: RecyclerView,
            viewHolder: ViewHolder, target: ViewHolder
        ): Boolean

        /**
         * Modified by add one param [viewHolder]
         *
         * @param viewHolder ViewHolder
         * @return Boolean
         */
        open fun isLongPressDragEnabled(viewHolder: ViewHolder): Boolean {
            return true
        }

        /**
         * Modified by add one param [viewHolder]
         *
         * @param viewHolder ViewHolder
         * @return Boolean
         */
        open fun isItemViewSwipeEnabled(viewHolder: ViewHolder?): Boolean {
            return if (viewHolder == null) false else true
        }

        open fun getBoundingBoxMargin(): Int {
            return 0
        }

        open fun getSwipeThreshold(viewHolder: ViewHolder?): Float {
            return .5f
        }

        open fun getMoveThreshold(viewHolder: ViewHolder?): Float {
            return .5f
        }


        open fun getSwipeEscapeVelocity(defaultValue: Float): Float {
            return defaultValue
        }


        open fun getSwipeVelocityThreshold(defaultValue: Float): Float {
            return defaultValue
        }


        fun chooseDropTarget(
            selected: ViewHolder,
            dropTargets: List<ViewHolder>, curX: Int, curY: Int
        ): ViewHolder? {
            val right = curX + selected.itemView.width
            val bottom = curY + selected.itemView.height
            var winner: ViewHolder? = null
            var winnerScore = -1
            val dx = curX - selected.itemView.left
            val dy = curY - selected.itemView.top
            val targetsSize = dropTargets.size
            for (i in 0 until targetsSize) {
                val target = dropTargets[i]
                if (dx > 0) {
                    val diff = target.itemView.right - right
                    if (diff < 0 && target.itemView.right > selected.itemView.right) {
                        val score = Math.abs(diff)
                        if (score > winnerScore) {
                            winnerScore = score
                            winner = target
                        }
                    }
                }
                if (dx < 0) {
                    val diff = target.itemView.left - curX
                    if (diff > 0 && target.itemView.left < selected.itemView.left) {
                        val score = Math.abs(diff)
                        if (score > winnerScore) {
                            winnerScore = score
                            winner = target
                        }
                    }
                }
                if (dy < 0) {
                    val diff = target.itemView.top - curY
                    if (diff > 0 && target.itemView.top < selected.itemView.top) {
                        val score = Math.abs(diff)
                        if (score > winnerScore) {
                            winnerScore = score
                            winner = target
                        }
                    }
                }
                if (dy > 0) {
                    val diff = target.itemView.bottom - bottom
                    if (diff < 0 && target.itemView.bottom > selected.itemView.bottom) {
                        val score = Math.abs(diff)
                        if (score > winnerScore) {
                            winnerScore = score
                            winner = target
                        }
                    }
                }
            }
            return winner
        }

        abstract fun onSwiped(viewHolder: ViewHolder?, direction: Int)


        fun getItemFrontView(viewHolder: ViewHolder?): View? {
            if (viewHolder == null) return null
            return if (viewHolder.itemView is ViewGroup && (viewHolder.itemView as ViewGroup).childCount > 1) {
                val viewGroup = viewHolder.itemView as ViewGroup
                viewGroup.getChildAt(viewGroup.childCount - 1)
            } else {
                viewHolder.itemView
            }
        }

        open fun onSelectedChanged(viewHolder: ViewHolder?, actionState: Int) {
            if (viewHolder != null) {
                defaultUIUtil!!.onSelected(viewHolder.itemView)
            }
        }

        private fun getMaxDragScroll(recyclerView: RecyclerView?): Int {
            if (mCachedMaxScrollSpeed == -1) {
                mCachedMaxScrollSpeed = recyclerView!!.resources.getDimensionPixelSize(
                    R.dimen.dp_20
                )
            }
            return mCachedMaxScrollSpeed
        }

        open fun onMoved(
            recyclerView: RecyclerView?,
            viewHolder: ViewHolder,
            fromPos: Int,
            target: ViewHolder,
            toPos: Int,
            x: Int,
            y: Int
        ) {
            val layoutManager = recyclerView!!.layoutManager
            if (layoutManager is ViewDropHandler) {
                (layoutManager as ViewDropHandler).prepareForDrop(
                    viewHolder.itemView,
                    target.itemView, x, y
                )
                return
            }

            // if layout manager cannot handle it, do some guesswork
            if (layoutManager!!.canScrollHorizontally()) {
                val minLeft = layoutManager.getDecoratedLeft(target.itemView)
                if (minLeft <= recyclerView.paddingLeft) {
                    recyclerView.scrollToPosition(toPos)
                }
                val maxRight = layoutManager.getDecoratedRight(target.itemView)
                if (maxRight >= recyclerView.width - recyclerView.paddingRight) {
                    recyclerView.scrollToPosition(toPos)
                }
            }
            if (layoutManager.canScrollVertically()) {
                val minTop = layoutManager.getDecoratedTop(target.itemView)
                if (minTop <= recyclerView.paddingTop) {
                    recyclerView.scrollToPosition(toPos)
                }
                val maxBottom = layoutManager.getDecoratedBottom(target.itemView)
                if (maxBottom >= recyclerView.height - recyclerView.paddingBottom) {
                    recyclerView.scrollToPosition(toPos)
                }
            }
        }

        open fun onDraw(
            c: Canvas, parent: RecyclerView, selected: ViewHolder?,
            recoverAnimationList: MutableList<RecoverAnimation>,
            actionState: Int, dX: Float, dY: Float
        ) {
            val recoverAnimSize = recoverAnimationList.size
            for (i in 0 until recoverAnimSize) {
                val anim = recoverAnimationList[i]
                anim.update()
                val count = c.save()
                onChildDraw(
                    c, parent, anim.mViewHolder, anim.hitX, anim.mY, anim.mActionState,
                    false
                )
                c.restoreToCount(count)
            }
            if (selected != null) {
                val count = c.save()
                onChildDraw(c, parent, selected, dX, dY, actionState, true)
                c.restoreToCount(count)
            }
        }

        open fun onDrawOver(
            c: Canvas, parent: RecyclerView, selected: ViewHolder?,
            recoverAnimationList: MutableList<RecoverAnimation>,
            actionState: Int, dX: Float, dY: Float
        ) {
            val recoverAnimSize = recoverAnimationList.size
            for (i in 0 until recoverAnimSize) {
                val anim = recoverAnimationList[i]
                val count = c.save()
                onChildDrawOver(
                    c, parent, anim.mViewHolder, anim.hitX, anim.mY, anim.mActionState,
                    false
                )
                c.restoreToCount(count)
            }
            if (selected != null) {
                val count = c.save()
                onChildDrawOver(c, parent, selected, dX, dY, actionState, true)
                c.restoreToCount(count)
            }
            var hasRunningAnimation = false
            for (i in recoverAnimSize - 1 downTo 0) {
                val anim = recoverAnimationList[i]
                if (anim.mEnded && !anim.mIsPendingCleanup) {
                    recoverAnimationList.removeAt(i)
                } else if (!anim.mEnded) {
                    hasRunningAnimation = true
                }
            }
            if (hasRunningAnimation) {
                parent.invalidate()
            }
        }

        open fun clearView(recyclerView: RecyclerView?, viewHolder: ViewHolder) {
            defaultUIUtil!!.clearView(viewHolder.itemView)
        }

        open fun onChildDraw(
            c: Canvas?, recyclerView: RecyclerView?,
            viewHolder: ViewHolder,
            dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
        ) {
            defaultUIUtil!!.onDraw(
                c, recyclerView, viewHolder.itemView, dX, dY, actionState,
                isCurrentlyActive
            )
        }

        open fun onChildDrawOver(
            c: Canvas?, recyclerView: RecyclerView?,
            viewHolder: ViewHolder,
            dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
        ) {
            defaultUIUtil!!.onDrawOver(
                c, recyclerView, viewHolder.itemView, dX, dY, actionState,
                isCurrentlyActive
            )
        }


        open fun getAnimationDuration(
            recyclerView: RecyclerView?, animationType: Int,
            animateDx: Float, animateDy: Float
        ): Long {
            val itemAnimator = recyclerView!!.itemAnimator
            return if (itemAnimator == null) {
                if (animationType == ANIMATION_TYPE_DRAG) DEFAULT_DRAG_ANIMATION_DURATION.toLong() else DEFAULT_SWIPE_ANIMATION_DURATION.toLong()
            } else {
                if (animationType == ANIMATION_TYPE_DRAG) itemAnimator.moveDuration else itemAnimator.removeDuration
            }
        }

        fun interpolateOutOfBoundsScroll(
            recyclerView: RecyclerView?,
            viewSize: Int, viewSizeOutOfBounds: Int,
            totalSize: Int, msSinceStartScroll: Long
        ): Int {
            val maxScroll = getMaxDragScroll(recyclerView)
            val absOutOfBounds = Math.abs(viewSizeOutOfBounds)
            val direction = Math.signum(viewSizeOutOfBounds.toFloat()).toInt()
            // might be negative if other direction
            val outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize)
            val cappedScroll = (direction * maxScroll *
                    sDragViewScrollCapInterpolator.getInterpolation(
                        outOfBoundsRatio
                    )).toInt()
            val timeRatio: Float
            timeRatio =
                if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
                    1f
                } else {
                    msSinceStartScroll.toFloat() / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS
                }
            val value = (cappedScroll * sDragScrollInterpolator
                .getInterpolation(timeRatio)).toInt()
            return if (value == 0) {
                if (viewSizeOutOfBounds > 0) 1 else -1
            } else value
        }
    }

    private inner class ItemTouchHelperGestureListener : SimpleOnGestureListener() {
        override fun onDown(e: MotionEvent): Boolean {
            return true
        }

        override fun onLongPress(e: MotionEvent) {
            val child = findChildView(e)
            if (child != null) {
                val vh = mRecyclerView!!.getChildViewHolder(child)
                if (vh != null) {
                    if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
                        return
                    }
                    val pointerId = MotionEventCompat.getPointerId(e, 0)
                    // Long press is deferred.
                    // Check w/ active pointer id to avoid selecting after motion
                    // event is canceled.
                    if (pointerId == mActivePointerId) {
                        val index = MotionEventCompat
                            .findPointerIndex(e, mActivePointerId)
                        val x = MotionEventCompat.getX(e, index)
                        val y = MotionEventCompat.getY(e, index)
                        mInitialTouchX = x
                        mInitialTouchY = y
                        mDy = 0f
                        mDx = mDy
                        if (DEBUG) {
                            Log.d(
                                TAG,
                                "onlong press: x:$mInitialTouchX,y:$mInitialTouchY"
                            )
                        }
                        if (mCallback.isLongPressDragEnabled(vh)) {
                            select(vh, ACTION_STATE_DRAG)
                        }
                    }
                }
            }
        }

        override fun onContextClick(e: MotionEvent): Boolean {
            return super.onContextClick(e)
        }

        override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
            Log.d(TAG, "onSingleTapConfirmed: $e")
            return true
        }
    }

    open inner class RecoverAnimation(
        val mViewHolder: ViewHolder,
        val mAnimationType: Int,
        val mActionState: Int,
        val mStartDx: Float,
        val mStartDy: Float,
        val mTargetX: Float,
        val mTargetY: Float
    ) : Animator.AnimatorListener {
        private val mValueAnimator: ValueAnimator
        var mIsPendingCleanup = false
        var hitX = 0f
        var mY = 0f

        // if user starts touching a recovering view, we put it into interaction mode again,
        // instantly.
        var mOverridden = false
        var mEnded = false
        private var mFraction = 0f
        fun setDuration(duration: Long) {
            mValueAnimator.duration = duration
        }

        fun start() {
            mViewHolder.setIsRecyclable(false)
            mValueAnimator.start()
        }

        fun cancel() {
            mValueAnimator.cancel()
        }

        fun setFraction(fraction: Float) {
            mFraction = fraction
        }


        fun update() {
            /*if (mStartDx == mTargetX) {
//                mX = ViewCompat.getTranslationX(mViewHolder.itemView);
            } else */
            run { this.hitX = mStartDx + mFraction * (mTargetX - mStartDx) }
            mY = if (mStartDy == mTargetY) {
                ViewCompat.getTranslationY(mViewHolder.itemView)
            } else {
                mStartDy + mFraction * (mTargetY - mStartDy)
            }
        }

        val hitY: Float
            get() = mViewHolder.itemView.y + mY

        override fun onAnimationStart(animation: Animator) {}
        override fun onAnimationEnd(animation: Animator) {
            if (!mEnded) {
                mViewHolder.setIsRecyclable(true)
            }
            mEnded = true
        }

        override fun onAnimationCancel(animation: Animator) {
            setFraction(1f) //make sure we recover the view's state.
        }

        override fun onAnimationRepeat(animation: Animator) {}

        init {
            mValueAnimator = ValueAnimator.ofFloat(0f, 1f)
            mValueAnimator.addUpdateListener { animation -> setFraction(animation.animatedFraction) }
            mValueAnimator.setTarget(mViewHolder.itemView)
            mValueAnimator.addListener(this)
            setFraction(0f)
        }
    }

    companion object {
        /**
         * Up direction, used for swipe & drag control.
         */
        const val UP = 1

        /**
         * Down direction, used for swipe & drag control.
         */
        const val DOWN = 1 shl 1

        /**
         * Left direction, used for swipe & drag control.
         */
        const val LEFT = 1 shl 2

        /**
         * Right direction, used for swipe & drag control.
         */
        const val RIGHT = 1 shl 3
        // If you change these relative direction values, update Callback#convertToAbsoluteDirection,
        // Callback#convertToRelativeDirection.
        /**
         * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
         * direction. Used for swipe & drag cwontrol.
         */
        const val START = LEFT shl 2

        /**
         * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
         * direction. Used for swipe & drag control.
         */
        const val END = RIGHT shl 2

        /**
         * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
         * the user or latest motion events have not yet triggered a swipe or drag.
         */
        const val ACTION_STATE_IDLE = 0

        /**
         * A View is currently being swiped.
         */
        const val ACTION_STATE_SWIPE = 1

        /**
         * A View is currently being dragged.
         */
        const val ACTION_STATE_DRAG = 2

        /**
         * Animation type for views which are swiped successfully.
         */
        const val ANIMATION_TYPE_SWIPE_SUCCESS = 1 shl 1

        /**
         * Animation type for views which are not completely swiped thus will animate back to their
         * original position.
         */
        const val ANIMATION_TYPE_SWIPE_CANCEL = 1 shl 2

        /**
         * Animation type for views that were dragged and now will animate to their final position.
         */
        const val ANIMATION_TYPE_DRAG = 1 shl 3
        private const val TAG = "ItemTouchHelper"
        private const val DEBUG = false
        private const val ACTIVE_POINTER_ID_NONE = -1
        private const val DIRECTION_FLAG_COUNT = 8
        private const val ACTION_MODE_IDLE_MASK = (1 shl DIRECTION_FLAG_COUNT) - 1
        private const val ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK shl DIRECTION_FLAG_COUNT
        private const val ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK shl DIRECTION_FLAG_COUNT

        /**
         * The unit we are using to track velocity
         */
        private const val PIXELS_PER_SECOND = 1000

        private fun hitTest(child: View, x: Float, y: Float, left: Float, top: Float): Boolean {
            return x >= left && x <= left + child.width && y >= top && y <= top + child.height
        }
    }
}